﻿using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Linq;
using Citi.Scheduler.Domain.Utils;
using Status = Citi.Scheduler.Domain.RoutineExecutionStatus;

namespace Citi.Scheduler.Domain
{
    public partial class RoutineExecution
    {
        #region Status Properties

        public bool ExecutedOrInProgress
        {
            get
            {
                if (LastHistory == null) return false;

                var lastStatus = (RoutineExecutionStatus)LastHistory.Status;

                return lastStatus != RoutineExecutionStatus.Scheduled
                       && lastStatus != RoutineExecutionStatus.WaitingFreeMachine
                       && lastStatus != RoutineExecutionStatus.WaitingPredecessorRoutine
                       && lastStatus != RoutineExecutionStatus.WaitingSpecificFreeMachine
                       && lastStatus != RoutineExecutionStatus.PredecessorRoutineWithError
                       && lastStatus != RoutineExecutionStatus.PredecessorNotScheduled
                       && lastStatus != RoutineExecutionStatus.Canceled
                       && lastStatus != RoutineExecutionStatus.CanceledAfterWaiting
                       && lastStatus != RoutineExecutionStatus.Stopped;

            }
        }

        public bool IsTimedOut
        {
            get
            {
                return StartsOn.HasValue && StartsOn.Value.Date == DateTime.Today
                        && (DateTime.Now - StartsOn.Value).TotalMinutes > Routine.EstimatedTime * (Routine.PercentageAcceptableVariation / 100 + 1) + 15;
            }
        }

        //public int TotalEstimatedMinutes
        //{
        //    return Routine.TotalEstimatedMinutes;// + Routine.TotalEstimatedMinutes * Routine.
        //}

        public bool InProgress
        {
            get
            {
                if (LastHistory == null) return false;

                return (RoutineExecutionStatus)LastHistory.Status == RoutineExecutionStatus.InProgress;
            }
        }

        public bool FinishedExecution
        {
            get
            {
                if (LastHistory == null) return false;

                return (RoutineExecutionStatus)LastHistory.Status != RoutineExecutionStatus.InProgress
                       && ExecutedOrInProgress;
            }
        }

        private bool IsLastHistory;

        public RoutineExecutionHistory LastHistory
        {
            get
            {
                if (!IsLastHistory && (ScheduledDateTime.Date == DateTime.Today
                    && (!RoutineExecutionHistories.Any() || (DateTime.Now - RoutineExecutionHistories.Last().Date).TotalMinutes < 15)))
                {
                    try
                    {
                        Context.Instance.LoadProperty(this, y => y.RoutineExecutionHistories, MergeOption.OverwriteChanges);
                        IsLastHistory = true;
                    }
                    catch { }
                }

                return RoutineExecutionHistories.LastOrDefault();
            }
        }

        public bool Error
        {
            get
            {
                if (LastHistory == null) return false;

                var lastStatus = (RoutineExecutionStatus)LastHistory.Status;

                return lastStatus == RoutineExecutionStatus.Error
                       || lastStatus == RoutineExecutionStatus.ExecutedWithError
                       || lastStatus == RoutineExecutionStatus.Stopped;
            }
        }

        public bool ExecutedWithoutError
        {
            get
            {
                if (LastHistory == null) return false;

                return (RoutineExecutionStatus)LastHistory.Status == RoutineExecutionStatus.Executed;
            }
        }

        public bool Canceled
        {
            get
            {
                if (LastHistory == null) return false;

                var lastStatus = (RoutineExecutionStatus)LastHistory.Status;

                return lastStatus == RoutineExecutionStatus.Canceled || lastStatus == RoutineExecutionStatus.CanceledAfterWaiting;
            }
        }

        #endregion

        public IEnumerable<RoutineDashboard> GetRoutineDashboard(DateTime startDate, DateTime endDate)
        {
            return Context.Instance.GetRoutineDashboard(startDate, endDate)
                .GroupBy(c => new { c.Routine, c.ScheduledDateTime.Date })
                .Select(g => g.OrderByDescending(c => c.EndsOn).First());
        }

        public void UpdateStatus(bool refreshPredecessor, int? routineIdPredecessor = null)
        {
            //var routinesExecution = SelectAllActive<RoutineExecution>()
            //    .Where(x => x.StartsOn == null && x.EndsOn == null && x.RoutineExecutionHistories.Any() && !x.ExecutedOrInProgress && x.LastHistory.Status != (short)Status.Canceled
            //            && x.LastHistory.Status != (short)Status.CanceledAfterWaiting
            //            && (!routineIdPredecessor.HasValue || (x.Routine.PredecessorRoutineId.HasValue && x.Routine.PredecessorRoutineId == routineIdPredecessor.Value))).ToList();            
            var routinesExecution = SelectAllActive<RoutineExecution>()
                .Where(x => x.StartsOn == null && x.EndsOn == null
                        && x.RoutineExecutionHistories.Any(y => y.Status != (short)Status.Executed && y.Status != (short)Status.InProgress
                        && y.Status != (short)Status.Canceled && y.Status != (short)Status.CanceledAfterWaiting
                        && y.Status != (short)Status.Error && y.Status != (short)Status.Stopped)
                        && (!routineIdPredecessor.HasValue || (x.Routine.PredecessorRoutineId.HasValue && x.Routine.PredecessorRoutineId == routineIdPredecessor.Value))).ToList();

            Console.WriteLine("{1} Checking status of {0} executions on queue", routinesExecution.Count, DateTime.Now.ToString("dd/MM/yyy HH:mm:ss"));

            Context.Instance.Refresh(RefreshMode.StoreWins, routinesExecution);

            var updated = 0;
            routinesExecution.ForEach(x =>
                {
                    if (x.Routine.HasPredecessor && !refreshPredecessor && x.LastHistory != null
                        && (RoutineExecutionStatus)x.LastHistory.Status == RoutineExecutionStatus.WaitingPredecessorRoutine)
                        return;

                    Context.Instance.LoadProperty(x, y => y.RoutineExecutionHistories, MergeOption.OverwriteChanges);
                    Context.Instance.Refresh(RefreshMode.StoreWins, x);
                    Context.Instance.Refresh(RefreshMode.StoreWins, x.RoutineExecutionHistories);
                    //Context.Instance.LoadProperty(x, y => y.StartsOn, MergeOption.OverwriteChanges);

                    if (x.EndsOn != null || x.StartsOn != null || x.ExecutedOrInProgress
                        || x.RoutineExecutionHistories
                            .Any(y => y.Status == (short)Status.Canceled || y.Status == (short)Status.CanceledAfterWaiting
                                || y.Status == (short)Status.Stopped || y.Status == (short)Status.Error
                                || y.Status == (short)Status.Executed || y.Status == (short)Status.InProgress))
                        return;

                    var status = RoutineSchedule.GetStatus(x.Routine, x.ScheduledDateTime, x.Id);

                    if (!x.RoutineExecutionHistories.Any() || (short)status == x.LastHistory.Status) return;

                    RoutineExecutionHistory.InsertHistory(x.Id, status);
                    updated++;
                });

            Console.WriteLine("{1} Updated {0} execution status", updated, DateTime.Now.ToString("dd/MM/yyy HH:mm:ss"));
        }

        public RoutineExecution GetWaitingFreeMachineRoutine(int machineId)
        {
            var routineId = Context.Instance.GetWaitingFreeMachineRoutines(machineId).First();

            if (!routineId.HasValue)
                return null;
            else
                return SelectById<RoutineExecution>(routineId.Value);
            //return GetAllWaitingFreeMachineRoutine(machineId);//.FirstOrDefault();
        }

        public RoutineExecution GetScheduleSpecificMachine(int machineId)
        {
            var machine = new Machine();

            var nextToRun = SelectAllActive<RoutineExecution>()
                .Where(x => x.ScheduledDateTime >= DateTime.Now && x.Routine.Active
                    && (x.MachineId == machineId || (x.Machine == null && machine.SelectByRoutineConfigurations(x.RoutineId).Any(y => y.Id == machineId))))
                    .OrderBy(x => x.ScheduledDateTime).ToList();

            nextToRun.ForEach(x => Context.Instance.LoadProperty(x, y => y.RoutineExecutionHistories, MergeOption.OverwriteChanges));

            var routines = nextToRun.Where(x => x.LastHistory != null
                && x.LastHistory.Status == (short)Status.Scheduled);

            return routines.FirstOrDefault(x => x.MachineId == machineId);
        }

        public IEnumerable<RoutineExecution> GetAllWaitingFreeMachineRoutine(int machineId)
        {
            //Context.SetOverwriteChanges(Context.Instance.RoutineExecutions);
            //Context.SetOverwriteChanges(Context.Instance.Routines);
            //Context.SetOverwriteChanges(Context.Instance.RoutineSchedules);
            //Context.SetOverwriteChanges(Context.Instance.Schedules);
            //Context.SetOverwriteChanges(Context.Instance.RoutineExecutions);
            //Context.SetOverwriteChanges(Context.Instance.RoutineOnDemands);
            //Context.SetOverwriteChanges(Context.Instance.RoutineExecutionHistories);

            var machine = new Machine();

            var nextToRun = SelectAllActive<RoutineExecution>()
                .Where(x => x.ScheduledDateTime <= DateTime.Now && x.Routine.Active
                    && (x.MachineId == machineId || (x.Machine == null && machine.SelectByRoutineConfigurations(x.RoutineId).Any(y => y.Id == machineId))))
                    .OrderBy(x => x.ScheduledDateTime).ToList();

            //nextToRun.ForEach(x => Context.Instance.LoadProperty(x, y => y.RoutineExecutionHistories, MergeOption.OverwriteChanges));

            return nextToRun.Where(x => x.LastHistory != null
                && (x.LastHistory.Status == (short)Status.WaitingFreeMachine || x.LastHistory.Status == (short)Status.WaitingSpecificFreeMachine));
        }

        public IEnumerable<RoutineExecution> GetByDate(DateTime date, int routineId = 0, int categoryId = 0)
        {
            var routines = categoryId != 0 ?
                GetAllChildrenRoutines(categoryId)
                : SelectAllActive<RoutineExecution>().Where(x => (x.RoutineId == routineId || routineId == 0));

            return routines.Where(x => x.ScheduledDateTime.Month == date.Month && x.ScheduledDateTime.Year == date.Year);
        }

        public IEnumerable<RoutineExecution> GetAllChildrenRoutinesByDate(int categoryId, DateTime inicialDate, DateTime finishDate)
        {
            return GetAllChildrenRoutines(categoryId).Where(x => x.ScheduledDateTime.Date >= inicialDate.Date
                && x.ScheduledDateTime.Date <= finishDate.Date);
        }

        public IEnumerable<RoutineExecution> GetAllChildrenRoutines(int categoryId)
        {
            //var routines = SelectAllActive<RoutineExecution>().Where(x => x.Routine.CategoryId == categoryId).ToList();
            var routines = new Routine().SelectAllActive<Routine>().Where(x => x.CategoryId == categoryId).ToList();

            var routinesExecutions = new List<RoutineExecution>();

            routines.ForEach(x => routinesExecutions.AddRange(x.RoutineExecutions));

            if (!routinesExecutions.Any()) return new List<RoutineExecution>();

            foreach (var child in routinesExecutions.First().Routine.Category.Children)
                routinesExecutions.AddRange(GetAllChildrenRoutines(child.Id));

            return routinesExecutions;
        }

        public void SetEndsOn()
        {
            Context.Instance.SetEndsOn(Id);
        }

        public static void InsertNewTentative(RoutineExecution execution, RoutineExecutionStatus currentStatus)
        {
            execution.SetEndsOn();
            //try
            //{
            //    if (!execution.EndsOn.HasValue)
            //    {
            //        execution.EndsOn = DateTime.Now;
            //        execution.Save(execution);
            //    }
            //}
            //catch { }

            if (execution.Tentatives >= execution.Routine.TimesToTryAgain) return;

            var nextRoutines = execution.SelectAll<RoutineExecution>()
                .Any(x => x.ScheduledDateTime.Date == execution.ScheduledDateTime.Date && x.RoutineId == execution.RoutineId && x.Id > execution.Id
                 && x.Tentatives > execution.Tentatives);

            if (nextRoutines) return;

            var newExecution = new RoutineExecution
            {
                Machine = execution.Routine.Machine,
                Tentatives = (short)(execution.Tentatives + 1),
                Routine = execution.Routine,
                ScheduledDateTime = DateTime.Now.AddMinutes(execution.Routine.TryAgainAfterMinutes),
                CustomerSLA = execution.CustomerSLA
            };

            //execution.Routine.RoutineExecutions.Where(y => y.ScheduledDateTime.Date == DateTime.Today && !y.ExecutedOrInProgress).ToList()
            //           .ForEach(notExecuted => RoutineExecutionHistory.InsertHistory(notExecuted.Id, RoutineExecutionStatus.Canceled, HistoryDetail.CanceledNewExecution));

            newExecution.Save(newExecution);

            RoutineExecutionHistory.InsertHistory(execution.Id, currentStatus, string.Format(HistoryDetail.RecoveryError, execution.Routine.TryAgainAfterMinutes));
            RoutineExecutionHistory.InsertHistory(newExecution.Id, RoutineExecutionStatus.Scheduled, HistoryDetail.InsertedBySystem);
        }

        public IEnumerable<RoutineExecution> GetByRoutineId(int routineId)
        {
            return SelectAllActive<RoutineExecution>().Where(x => x.RoutineId == routineId).ToList();
        }

        public IEnumerable<RoutineExecution> GetByMachineId(int machineId = 0)
        {
            return SelectAllActive<RoutineExecution>().Where(x => machineId == 0 || x.MachineId == machineId).ToList();
        }

        public IEnumerable<RoutineExecution> GetByMachineIdAndDate(DateTime startDate, DateTime endDate, int machineId = 0)
        {
            return GetByMachineId(machineId).Where(x => x.ScheduledDateTime.Date >= startDate.Date && x.ScheduledDateTime <= endDate.Date).ToList();
        }

        public IEnumerable<RoutineExecution> GetByMachineIdAndDate(DateTime date, int machineId = 0)
        {
            return GetByMachineId(machineId).Where(x => x.ScheduledDateTime.Date == date.Date).ToList();
        }
    }
}
